// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: donggu

#include "coin2/exchange/base/feed/ws/feed_subscription_spec.h"

std::unordered_map<MarketExchangeApi, FeedSubscriptionSpec> FeedSubscriptionSpec::FromToml(
    const std::experimental::filesystem::path& path) {
  std::unordered_map<MarketExchangeApi, FeedSubscriptionSpec> map;
  const auto table = toml::parse(path).as_table();
  for (const auto& pair : table) {
    const auto& entry = pair.second.as_table();
    FeedSubscriptionSpec spec;
    auto mea = MarketExchangeApi::FromSnakeStringNoThrow(pair.first);
    if (!mea.has_value()) {
      continue;
    }
    spec.mea = mea.value();
    spec.host = entry.at("host").as_string();
    spec.port = entry.at("port").as_string();
    spec.path = entry.at("path").as_string();

    spec.decompress = entry.at("decompress").as_string();

    if (entry.find("set_tlsext_host_name") != entry.end()) {
      spec.set_tlsext_host_name = entry.at("set_tlsext_host_name").as_boolean();
    } else {
      spec.set_tlsext_host_name = false;
    }
    spec.support_ping_frame = entry.at("support_ping_frame").as_boolean();
    spec.handle_ping_msg = entry.at("handle_ping_msg").as_boolean();

    if (entry.find("reconnect_delay_ns") != entry.end()) {
      spec.reconnect_delay_ns = entry.at("reconnect_delay_ns").as_integer();
    } else {
      spec.reconnect_delay_ns = 0;
    }

    if (entry.find("require_auth") != entry.end()) {
      spec.require_auth = entry.at("require_auth").as_boolean();
    } else {
      spec.require_auth = false;
    }

    if (entry.find("require_header") != entry.end()) {
      spec.require_header = entry.at("require_header").as_boolean();
    } else {
      spec.require_header = false;
    }

    if (entry.find("require_regenerate_path") != entry.end()) {
      spec.require_regenerate_path = entry.at("require_regenerate_path").as_boolean();
    } else {
      spec.require_regenerate_path = false;
    }

    if (entry.find("permessage_deflate") != entry.end()) {
      spec.permessage_deflate = entry.at("permessage_deflate").as_boolean();
    } else {
      spec.permessage_deflate = false;
    }

    if (entry.find("use_periodic_resubscribe") != entry.end()) {
      spec.use_periodic_resubscribe = entry.at("use_periodic_resubscribe").as_boolean();
    } else {
      spec.use_periodic_resubscribe = false;
    }

    if (entry.find("resubscribe_interval_ns") != entry.end()) {
      spec.resubscribe_interval_ns = entry.at("resubscribe_interval_ns").as_integer();
    } else {
      spec.resubscribe_interval_ns = 24 * 60 * 60 * 1e9;
    }

    if (entry.find("periodic_ping_msg") != entry.end()) {
      spec.periodic_ping_msg = entry.at("periodic_ping_msg").as_string();
    } else {
      spec.periodic_ping_msg = {};
    }

    if (entry.find("ping_interval_ns") != entry.end()) {
      spec.ping_interval_ns = entry.at("ping_interval_ns").as_integer();
    } else {
      spec.ping_interval_ns = 60 * 1e9;
    }

    if (entry.find("periodic_force_reconnect_minute") != entry.end()) {
      spec.periodic_force_reconnect_minute = entry.at(
          "periodic_force_reconnect_minute").as_integer();
    } else {
      spec.periodic_force_reconnect_minute = 0;
    }

    if (entry.find("symbols_per_connection") != entry.end()) {
      spec.symbols_per_connection = entry.at(
          "symbols_per_connection").as_integer();
    } else {
      spec.symbols_per_connection = 0;
    }

    if (entry.find("unauthorized_limit") != entry.end()) {
      spec.unauthorized_limit = WsRateLimit::ParseToml(entry.at("unauthorized_limit").as_table());
    }
    if (entry.find("authorized_limit") != entry.end()) {
      spec.authorized_limit = WsRateLimit::ParseToml(entry.at("authorized_limit").as_table());
    }

    spec.subscription_req = entry.at("subscription_req").as_string();
    if (entry.find("subscription_resp") != entry.end()) {
      spec.subscription_resp = entry.at("subscription_resp").as_string();
    }
    if (entry.find("unsubscription_req") != entry.end()) {
      spec.unsubscription_req = entry.at("unsubscription_req").as_string();
    }
    if (entry.find("unsubscription_resp") != entry.end()) {
      spec.unsubscription_resp = entry.at("unsubscription_resp").as_string();
    }

    if (entry.find("channels") != entry.end()) {
      const auto& channels = entry.at("channels").as_table();
      for (const auto& [name, ch] : channels) {
        spec.channels[name] = ch.as_string();
      }
    }

    if (entry.find("recipes") != entry.end()) {
      const auto& recipes = entry.at("recipes").as_table();
      for (const auto& [name, ch_list] : recipes) {
        auto& list = spec.recipes[name];
        for (const auto& ch_name : ch_list.as_array()) {
          list.push_back(ch_name.as_string());
        }
      }
    }

    if (entry.find("default_recipe") != entry.end()) {
      spec.default_recipe = entry.at("default_recipe").as_string();
    }

    if (entry.find("urls") != entry.end()) {
      const auto& urls = entry.at("urls").as_table();
      for (const auto& [name, url] : urls) {
        WsUrl ws_url = {spec.host, spec.port, spec.path, {} /* method */, {} /* proxy*/,
            name, {} /* bind_ip */, spec.subscription_req,
            spec.unauthorized_limit, false, {}, false, spec.support_ping_frame};

        // host, port, path, channels
        for (const auto& [k, v] : url.as_table()) {
          if (k == "channels") {
            for (const auto& ch_name : v.as_array()) {
              ws_url.channels.push_back(ch_name.as_string());
            }
          } else if (k == "host") {
            ws_url.host = v.as_string();
          } else if (k == "port") {
            ws_url.port = v.as_string();
          } else if (k == "path") {
            ws_url.path = v.as_string();
          } else if (k == "method") {
            ws_url.method = v.as_string();
          } else if (k == "subscription_req") {
            ws_url.request = v.as_string();
          } else if (k == "unauthorized_limit") {
            ws_url.unauthorized_limit = WsRateLimit::ParseToml(v.as_table());
          } else if (k == "wrap_response") {
            ws_url.wrap_response = v.as_boolean();
          } else if (k == "support_ping_frame") {
            ws_url.support_ping_frame = v.as_boolean();
          } else {
            THROW() << "Not support URL property " << k;
          }
        }
        spec.extra_urls[name] = ws_url;
      }
    }

    map[spec.mea] = spec;
  }
  return map;
}
